﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cofe.Core.Implements;
using Cofe.Core.Property;
using Cofe.Core;
using Newtonsoft.Json.Linq;
using Cofe.Core.Utils;
using Cofe.Core.Interfaces;
using System.Reflection;
using Cofe.Core.Entry;
using System.ComponentModel.Composition;
using Cofe.Core.Filter;

namespace Cofe.Web
{
    [Export(typeof(ICofeService))]
    [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFE)]
    public class EntryJObjectSerializer : IEntryJObjectSerializer
    {
        #region Constructor

        #endregion

        #region Methods

        #region Helper Methods

        private static MethodInfo _jTokenValue = typeof(Newtonsoft.Json.Linq.Extensions).GetMethods(BindingFlags.Public | BindingFlags.Static).First
    (mi => mi.Name == "Value" && mi.GetGenericArguments().Length == 1);
        protected object getValueFromJToken(JToken token, Type type)
        {
            //token.Value<int>(
            return _jTokenValue.MakeGenericMethod(type).Invoke(null, new object[] { token });
        }
        protected string camelCase(string str)
        {
            return str.Substring(0, 1).ToLower() + str.Substring(1);
        }

        protected string valueToString(object value)
        {
            if (value == null)
                return "";
            if (value is Guid)
                return ((Guid)value).ToString("n");
            else return value.ToString();
        }

        protected virtual object getValue(CofeSystemInfo entry, object prop)
        {
            return entry.Properties.Behaviors.GetProperty(prop).Value;
        }

        protected virtual string getValueAsString(CofeSystemInfo entry, object prop)
        {
            return valueToString(getValue(entry, prop));
        }

        protected class propInfo
        {
            public List<KeyValuePair<object, WebMetadataAttribute>> attribeMetadataProps { get; set; }
            public List<KeyValuePair<object, WebMetadataAttribute>> elementMetadataProps { get; set; }
            public IEnumerable<KeyValuePair<object, WebMetadataAttribute>> metadataProps
            {
                get { return attribeMetadataProps.Union(elementMetadataProps); }
            }
        }

        protected propInfo getProps<T>(T entry, bool includeDetails) where T : CofeSystemInfo
        {
            var props = entry.Properties.Behaviors.GetSupportedProperties();
            var metadataProps = props.Where(p => DecoratorExtension.IsMetadata(p))
                .ToDictionary(p => p, p => AttributeUtils<WebMetadataAttribute>.FindAttribute(p));

            return new propInfo()
            {
                attribeMetadataProps = metadataProps
                    .Where(pp => includeDetails || pp.Value.IsMajorProperty<T>())
                    .Where(pp => pp.Value.SerializeAsAttribute).ToList(),

                elementMetadataProps = metadataProps
                    .Where(pp => includeDetails || pp.Value.IsMajorProperty<T>())
                    .Where(pp => !pp.Value.SerializeAsAttribute).ToList()
            };
        }
        #endregion


        protected void WriteParameters(ParameterDic pd, JObject outputObject)
        {
            if (pd != null)
            {
                Func<string, string> parameterFilter = (key) => key.StartsWith("out_") ? key.Replace("out_", "") : null;
                foreach (var ppair in pd)
                    if (parameterFilter(ppair.Key) != null)
                        if (ppair.Value.GetType().IsPrimitive)
                            outputObject.Add(camelCase(parameterFilter(ppair.Key)), new JValue(ppair.Value));
                        else outputObject.Add(camelCase(parameterFilter(ppair.Key)), new JValue(ppair.Value.ToString()));
            }
        }

        protected virtual JObject WriteLink(XmlLink link)
        {
            var retVal = new JObject();

            retVal.Add("mediaType", link.MediaTypeStr);
            retVal.Add("rel", link.RelationTypeStr);
            retVal.Add("uri", link.Uri.AbsoluteUri);

            return retVal;
        }

        protected virtual JArray xmlLinkToJArray(IEnumerable<XmlLink> links)
        {
            return new JArray(from l in links select WriteLink(l));
        }

        protected virtual XmlLink ReadLink(JObject data)
        {
            string mediaType = data.Value<string>("mediaType");
            string rel = data.Value<string>("rel");
            string uri = data.Value<string>("uri");
            return new XmlLink() { MediaTypeStr = mediaType, RelationTypeStr = rel, Uri = new Uri(uri) };
        }

        public virtual JObject WriteEntry<T>(T entry, bool includeDetails = false, bool writeLinks = true) where T : CofeSystemInfo
        {
            var props = getProps(entry, includeDetails);
            var retVal = new JObject();

            foreach (var ppair in props.metadataProps)
            {
                string propKey = camelCase(CofeServices.PropertyDictionary.GetPropertyString(ppair.Key));
                retVal.Add(new JProperty(propKey, getValue(entry, ppair.Key)));
            }

            return retVal;
        }

        public virtual CofeSystemInfo ReadEntry(JObject data, params IEntrySerializerHelper[] helpers)
        {
            ParameterDic pd = ParameterDic.FromJObject(data, "parseName", "id", "name", "type");
            CofeSystemInfo entry = helpers.FindFunc(pd);

            if (entry == null)
                return null;

            entry.Properties.ChangeState(PropertyHostState.Default);

            ParameterDic pdv = ParameterDic.FromJObject(data, (k) => !pd.ContainsKey(k));
            helpers.UpdateFunc(entry, pdv);

            return entry;
        }

        public virtual JObject WriteEntryList(WebEntryList entryList, bool includeDetails = false, bool writeLinks = true)
        {
            var retVal = new JObject();
            WriteParameters(entryList.Parameters, retVal);
            retVal.Add("entries",
                new JArray(from ie in entryList.Entries select WriteEntry((CofeSystemInfo)ie, includeDetails, writeLinks)));
            return retVal;
        }

        public virtual WebEntryList ReadEntryList(JObject data, params IEntrySerializerHelper[] helpers)
        {
            var retVal = new WebEntryList();

            if (data.SelectToken("entries") is JArray)
                retVal.Entries = from e in (JArray)data.SelectToken("entries")
                                 select ReadEntry((JObject)e, helpers);
            return retVal;
        }

        #endregion

        #region Data



        #endregion

        #region Public Properties



        #endregion



    }
}
